using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using Should;
using TLDRML;
using TLDRML.Tokens;

namespace TLDRMLTests
{
    public class LexerContext
    {
        public IList<Token> TokensTest(string input)
        {
            var lexer = new Lexer(new StringReader(input));
            var tokens = new List<Token>(lexer.Tokenize());
            return tokens;
        }
    }


    [TestFixture]
    public class LexerTests : LexerContext
    {
        [Test]
        public void Tokenize_Text_YieldsLiteralToken()
        {
            TokensTest("aa")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa")
                    });
        }

        [Test]
        public void Tokenize_TwoTextOnTwoLines_YieldsTwoLiteralTokens()
        {
            TokensTest("aa\nbb\n")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new NewLineToken(null),
                        new LiteralToken(null, "bb"),
                        new NewLineToken(null),
                    });
        }

        [Test]
        public void Tokenize_AttributeSyntax_YieldsAttributeToken()
        {
            TokensTest("aa  bb= cc")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new LiteralToken(null, "bb"),
                        new EqualsToken(null),
                        new LiteralToken(null, "cc")
                    });
        }

        [Test]
        public void Tokenize_TwoAttributes_YieldsTwoSetsOfTokens()
        {
            TokensTest("aa\t\tbb= cc  dd =ee")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new LiteralToken(null, "bb"),
                        new EqualsToken(null),
                        new LiteralToken(null, "cc"),
                        new LiteralToken(null, "dd"),
                        new EqualsToken(null),
                        new LiteralToken(null, "ee")
                    });
        }

        [Test]
        public void Tokenize_IndentedTextWithBlankLines_YieldsLiteralsAndIndent()
        {
            TokensTest(
                "aa\n" +
                "  bb\n" +
                "\n   \n \n" +
                "  cc"
                ).ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "bb"),
                        new NewLineToken(null),
                        new LiteralToken(null, "cc"),
                    }
                );
        }

        [Test]
        public void Tokenize_IndentedText_YieldsAllOutdentsAtTheEnd()
        {
            TokensTest(
                "aa\n" +
                "  bb\n" +
                "    cc\n  "
                ).ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "bb"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "cc"),
                        new NewLineToken(null),
                        new OutdentToken(null),
                        new OutdentToken(null),
                    }
                );
        }


        [Test]
        public void Tokenize_IndentedText_YieldsLiteralsAndIndent()
        {
            TokensTest(
                "aa\n  bb\n"
                ).ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "bb"),
                        new NewLineToken(null),
                        new OutdentToken(null)
                    }
                );
        }

        [Test]
        public void Tokenize_IndentedTextAfterTwoLiterals_YieldsLiteralsAndIndent()
        {
            TokensTest(
                "aa\nbb\n  cc\n"
                ).ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new NewLineToken(null),
                        new LiteralToken(null, "bb"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "cc"),
                        new NewLineToken(null),
                        new OutdentToken(null)
                    }
                );
        }

        [Test]
        public void Tokenize_VariablyIndentedText_YieldsLiteralsAndIndent()
        {
            TokensTest(
                "a\n" +
                " b\n" +
                "   c\n" +
                "   d\n" +
                " e\n" +
                "f\n" +
                "\tg\n" +
                "\t\th\n" +
                "i\n"
                ).ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "a"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "b"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "c"),
                        new NewLineToken(null),
                        new LiteralToken(null, "d"),
                        new NewLineToken(null),
                        new OutdentToken(null),
                        new LiteralToken(null, "e"),
                        new NewLineToken(null),
                        new OutdentToken(null),
                        new LiteralToken(null, "f"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "g"),
                        new NewLineToken(null),
                        new IndentToken(null),
                        new LiteralToken(null, "h"),
                        new NewLineToken(null),
                        new OutdentToken(null),
                        new OutdentToken(null),
                        new LiteralToken(null, "i"),
                        new NewLineToken(null),
                    }
                );
        }


        [Test]
        public void Tokenize_TextReturningFromTwoLevelsOfIndentation_YieldsTwoOutdentTokens()
        {
            TokensTest(
                "aa\n" +
                "  bb\n" +
                "    cc\n" +
                "dd\n"
                ).ShouldEqual(
                    new List<Token>
                    {
                            new LiteralToken(null, "aa"),
                            new NewLineToken(null),
                            new IndentToken(null),
                            new LiteralToken(null, "bb"),
                            new NewLineToken(null),
                            new IndentToken(null),
                            new LiteralToken(null, "cc"),
                            new NewLineToken(null),
                            new OutdentToken(null),
                            new OutdentToken(null),
                            new LiteralToken(null, "dd"),
                            new NewLineToken(null),
                    }
                );
        }

        [Test]
        public void Tokenize_QuotedText_YieldsStringToken()
        {
            TokensTest("\"aa\"")
                .ShouldEqual(
                    new List<Token>
                    {
                        new StringToken(null, "aa")
                    });
        }

        [Test]
        public void Tokenize_QuotedTextWithBackslash_YieldsStringToken()
        {
            TokensTest("\"a\\\\a\"")
                .ShouldEqual(
                    new List<Token>
                    {
                        new StringToken(null, "a\\a")
                    });
        }

        [Test]
        public void Tokenize_QuotedAttributeValue_YieldsStringToken()
        {
            TokensTest("aa bb=\"c c\"")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "aa"),
                        new LiteralToken(null, "bb"),
                        new EqualsToken(null),
                        new StringToken(null, "c c")
                    });
        }

        [Test]
        public void Tokenize_TextWithinQuotesContainsEscapedQuote_YieldsStringTokenWithQuote()
        {
            TokensTest("a=\"b\\\"b\"")
                .ShouldEqual(
                new List<Token>
                {
                    new LiteralToken(null, "a"),
                    new EqualsToken(null),
                    new StringToken(null, "b\"b")
                });
        }

        [Test]
        public void Tokenize_PoundSign_YieldsNameDeclaration()
        {
            TokensTest("a #b")
                .ShouldEqual(
                new List<Token>
                {
                    new LiteralToken(null, "a"),
                    new NameDeclarationToken(null),
                    new LiteralToken(null, "b")
                });
        }

        [Test]
        public void Tokenize_EndOfLine_YieldsNewLineToken()
        {
            TokensTest("a\nb\n")
                .ShouldEqual(
                    new List<Token>
                    {
                        new LiteralToken(null, "a"),
                        new NewLineToken(null),
                        new LiteralToken(null, "b"),
                        new NewLineToken(null)
                    });
        }
    }
}